/**
 * Copyright (c) 2012-2014, Yang Yang <cntototo@gmail.com>
 *
 * This software may be distributed under the terms of the New BSD License.
 * See README for more details.
 */

#include "loop.h"

#include <sys/types.h>
#include <unistd.h>

#define LOG_TAG "LOOP"

#include "include.h"
#include "def.h"
#include "util.h"
#include "log.h"
#include "queue.h"
#include "os.h"

struct wfs_loop_sock {
	int sock;
	void *ctx;
	wfs_loop_sock_handler handler;
	SLIST_ENTRY(wfs_loop_sock) list_item;
};

struct wfs_loop_sig {
	int sig;
	void *ctx;
	wfs_loop_sig_handler handler;
	int signaled;
	SLIST_ENTRY(wfs_loop_sig) list_item;
};

struct wfs_loop_timer {
	struct wfs_timeval tv;
	void *ctx;
	wfs_loop_timer_handler handler;
	SLIST_ENTRY(wfs_loop_timer) list_item;
};

/** Define a singly-linked list structure for wfs_loop_sock */
SLIST_HEAD(wfs_loop_sock_list, wfs_loop_sock);

/** Define a singly-linked list structure for wfs_loop_signal */
SLIST_HEAD(wfs_loop_sig_list, wfs_loop_sig);

/** Define a singly-linked list structure for wfs_loop_timer */
SLIST_HEAD(wfs_loop_timer_list, wfs_loop_timer);

struct wfs_loop_info {
	int max_sock;
	struct wfs_loop_sock_list readers;
	int reader_num;
	struct wfs_loop_sig_list signals;
	int signal_num;
	int signaled;
	struct wfs_loop_timer_list timers;
	int timer_num;
	int terminate;
};

static struct wfs_loop_info wfs_loop;

/***********************************************************
 *                      Local functions
 ***********************************************************
 */
static int is_sock_handler_registered(struct wfs_loop_sock_list *sock_list,
		int sock, wfs_loop_sock_handler handler, void *ctx,
		struct wfs_loop_sock **found)
{
	struct wfs_loop_sock *cur = NULL;

	assert(sock_list && handler && found);

	SLIST_FOREACH(cur, sock_list, list_item) {
		if (cur->sock == sock && cur->ctx == ctx &&
				cur->handler == handler) {
			*found = cur;
			return TRUE;
		}
	}

	return FALSE;
}


static int is_sig_handler_registered(struct wfs_loop_sig_list *sig_list,
		int sig, wfs_loop_sig_handler handler, void *ctx,
		struct wfs_loop_sig **found)
{
	struct wfs_loop_sig *cur = NULL;

	assert(sig_list && handler && found);

	SLIST_FOREACH(cur, sig_list, list_item) {
		if (cur->sig == sig && cur->ctx == ctx &&
				cur->handler == handler) {
			*found = cur;
			return TRUE;
		}
	}

	return FALSE;
}


static int is_timer_handler_registered(struct wfs_loop_timer_list *timer_list,
		wfs_loop_timer_handler handler, void *ctx,
		struct wfs_loop_timer **found)
{
	struct wfs_loop_timer *cur = NULL;

	assert(timer_list && handler && found);

	SLIST_FOREACH(cur, timer_list, list_item) {
		if (cur->ctx == ctx && cur->handler == handler) {
			*found = cur;
			return TRUE;
		}
	}

	return FALSE;
}


static void notify_signal(int sig)
{
	struct wfs_loop_sig *cur = NULL;

	++wfs_loop.signaled;
	SLIST_FOREACH(cur, &wfs_loop.signals, list_item) {
		if (cur->sig == sig) {
			++cur->signaled;
			break;
		}
	}
}


static void process_pending_signals(void)
{
	struct wfs_loop_sig *cur = NULL;

	if (wfs_loop.signaled == 0)
		return;

	wfs_loop.signaled = 0;
	SLIST_FOREACH(cur, &wfs_loop.signals, list_item) {
		if (cur->signaled) {
			cur->signaled = 0;
			cur->handler(cur->sig, cur->ctx);
		}
	}
}


static void sock_list_set_fds(struct wfs_loop_sock_list *sock_list, fd_set *fds)
{
	struct wfs_loop_sock *cur = NULL;

	assert(sock_list && fds);

	FD_ZERO(fds);

	SLIST_FOREACH(cur, sock_list, list_item)
		FD_SET(cur->sock, fds);
}


static void sock_list_dispatch(struct wfs_loop_sock_list *sock_list,
		fd_set *fds)
{
	struct wfs_loop_sock *cur = NULL;

	assert(sock_list && fds);

	SLIST_FOREACH(cur, sock_list, list_item) {
		if (FD_ISSET(cur->sock, fds))
			cur->handler(cur->sock, cur->ctx);
	}
}


/***********************************************************
 *                      Global functions
 ***********************************************************
 */
enum wfs_internal_err wfs_loop_register_read_sock(int sock,
		wfs_loop_sock_handler handler, void *ctx)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	struct wfs_loop_sock *found = NULL;
	struct wfs_loop_sock *new_sock = NULL;

	assert(handler);

	ENTER();

	if (wfs_loop.reader_num == 0)
		SLIST_INIT(&wfs_loop.readers);

	if (is_sock_handler_registered(&wfs_loop.readers, sock, handler,
			ctx, &found)) {
		ret = WFS_INTERNAL_ERR_INVAL;
		wfs_printf(LOG_ERR, "This read sock handler is already "
			"registered.");
		goto out;
	}

	new_sock = (struct wfs_loop_sock *) zalloc(sizeof(*new_sock));
	if (!new_sock) {
		ret = WFS_INTERNAL_ERR_OOM;
		wfs_printf(LOG_ERR, "%s", err_to_str(ret));
		goto out;
	}

	new_sock->sock = sock;
	new_sock->ctx = ctx;
	new_sock->handler = handler;

	SLIST_INSERT_HEAD(&wfs_loop.readers, new_sock, list_item);
	++wfs_loop.reader_num;
	if (wfs_loop.max_sock < sock)
		wfs_loop.max_sock = sock;

out:
	if (ret) {
		wfs_printf(LOG_ERR, "Failed to register read sock handler.");
		FREE(new_sock);
	}
	LEAVE();
	return ret;
}


enum wfs_internal_err wfs_loop_unregister_read_sock(int sock,
		wfs_loop_sock_handler handler, void *ctx)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	struct wfs_loop_sock *found = NULL;

	assert(handler);

	ENTER();

	if (!is_sock_handler_registered(&wfs_loop.readers, sock, handler,
			ctx, &found)) {
		wfs_printf(LOG_DEBUG, "This read sock handler is "
			"not registered, thus no need to unregister it.");
		goto out;
	}

	SLIST_REMOVE(&wfs_loop.readers, found, wfs_loop_sock, list_item);
	--wfs_loop.reader_num;

	FREE(found);

out:
	LEAVE();
	return ret;
}


enum wfs_internal_err wfs_loop_register_sig(int sig,
		wfs_loop_sig_handler handler, void *ctx)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	struct wfs_loop_sig *found = NULL;
	struct wfs_loop_sig *new_sig = NULL;

	assert(handler);

	ENTER();

	if (wfs_loop.signal_num == 0)
		SLIST_INIT(&wfs_loop.signals);

	if (is_sig_handler_registered(&wfs_loop.signals, sig, handler,
			ctx, &found)) {
		ret = WFS_INTERNAL_ERR_INVAL;
		wfs_printf(LOG_ERR, "This signal handler is already "
			"registered.");
		goto out;
	}

	new_sig = (struct wfs_loop_sig *) zalloc(sizeof(*new_sig));
	if (!new_sig) {
		ret = WFS_INTERNAL_ERR_OOM;
		wfs_printf(LOG_ERR, "%s", err_to_str(ret));
		goto out;
	}

	new_sig->sig = sig;
	new_sig->ctx = ctx;
	new_sig->handler = handler;

	SLIST_INSERT_HEAD(&wfs_loop.signals, new_sig, list_item);
	++wfs_loop.signal_num;

	signal(sig, notify_signal);

out:
	if (ret) {
		wfs_printf(LOG_ERR, "Failed to register signal handler.");
		FREE(new_sig);
	}
	LEAVE();
	return ret;
}


enum wfs_internal_err wfs_loop_unregister_sig(int sig,
		wfs_loop_sig_handler handler, void *ctx)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	struct wfs_loop_sig *found = NULL;

	assert(handler);

	ENTER();

	if (!is_sig_handler_registered(&wfs_loop.signals, sig, handler,
			ctx, &found)) {
		wfs_printf(LOG_DEBUG, "This signal handler is not registered, "
				"thus no need to unregister it.");
		goto out;
	}

	SLIST_REMOVE(&wfs_loop.signals, found, wfs_loop_sig, list_item);
	--wfs_loop.signal_num;

	FREE(found);

out:
	LEAVE();
	return ret;
}


enum wfs_internal_err wfs_loop_register_timer(unsigned int sec,
		unsigned int usec, wfs_loop_timer_handler handler, void *ctx)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	struct wfs_loop_timer *found = NULL;
	struct wfs_loop_timer *new_timer = NULL;
	struct wfs_loop_timer *cur = NULL;
	struct wfs_loop_timer *prev = NULL;

	assert(handler);

	ENTER();

	if (wfs_loop.timer_num == 0)
		SLIST_INIT(&wfs_loop.timers);

	if (is_timer_handler_registered(&wfs_loop.timers, handler,
			ctx, &found)) {
		ret = WFS_INTERNAL_ERR_INVAL;
		wfs_printf(LOG_ERR, "This timer handler is already "
			"registered.");
		goto out;
	}

	new_timer = (struct wfs_loop_timer *) zalloc(sizeof(*new_timer));
	if (!new_timer) {
		ret = WFS_INTERNAL_ERR_OOM;
		wfs_printf(LOG_ERR, "%s", err_to_str(ret));
		goto out;
	}

	ret = os_get_time(&new_timer->tv);
	if (ret) {
		wfs_printf(LOG_ERR, "Failed to get current time.");
		goto out;
	}

	ret = os_add_time_val(&new_timer->tv, sec, usec, &new_timer->tv);
	if (ret)
		goto out;

	new_timer->ctx = ctx;
	new_timer->handler = handler;

	/** Add the new timer into the sorted list */
	SLIST_FOREACH(cur, &wfs_loop.timers, list_item) {
		if (os_is_time_before(&new_timer->tv, &cur->tv))
			break;
		prev = cur;
	}

	if (prev)
		SLIST_INSERT_AFTER(prev, new_timer, list_item);
	else
		SLIST_INSERT_HEAD(&wfs_loop.timers, new_timer, list_item);

	++wfs_loop.timer_num;

out:
	if (ret) {
		wfs_printf(LOG_ERR, "Failed to register timer handler.");
		FREE(new_timer);
	}
	LEAVE();
	return ret;
}


enum wfs_internal_err wfs_loop_unregister_timer(wfs_loop_timer_handler handler,
		void *ctx)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	struct wfs_loop_timer *found = NULL;

	assert(handler);

	ENTER();

	if (!is_timer_handler_registered(&wfs_loop.timers, handler,
			ctx, &found)) {
		wfs_printf(LOG_DEBUG, "This timer handler is not registered, "
				"thus no need to unregister it.");
		goto out;
	}

	SLIST_REMOVE(&wfs_loop.timers, found, wfs_loop_timer, list_item);
	--wfs_loop.timer_num;

	FREE(found);

out:
	LEAVE();
	return ret;
}


enum wfs_internal_err wfs_loop_init(void)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;

	ENTER();

	memset(&wfs_loop, 0, sizeof(wfs_loop));

	LEAVE();
	return ret;
}


enum wfs_internal_err wfs_loop_terminate(void)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;

	ENTER();

	wfs_loop.terminate = 1;

	LEAVE();
	return ret;
}


void wfs_loop_run(void)
{
	struct wfs_timeval tv;
	struct wfs_timeval now;
	struct timeval tv_select;
	fd_set readfds;
	int ret = 0;
	struct wfs_loop_timer *cur_timer = NULL;

	ENTER();

	while (!wfs_loop.terminate && (wfs_loop.reader_num > 0 ||
			wfs_loop.signal_num > 0 || wfs_loop.timer_num > 0)) {
		cur_timer = SLIST_FIRST(&wfs_loop.timers);
		if (cur_timer) {
			if (os_get_time(&now)) {
				wfs_printf(LOG_ERR, "Failed to get "
					"current time.");
				goto out;
			}

			if (os_is_time_before(&now, &cur_timer->tv))
				os_sub_time(&cur_timer->tv, &now, &tv);
			else
				tv.sec = tv.usec = 0;
			tv_select.tv_sec = tv.sec;
			tv_select.tv_usec = tv.usec;
		}

		sock_list_set_fds(&wfs_loop.readers, &readfds);
		ret = select(wfs_loop.max_sock + 1, &readfds, NULL, NULL,
				cur_timer ? &tv_select : NULL);
		if (ret < 0 && errno != EINTR) {
			wfs_perror("select");
			goto out;
		}

		process_pending_signals();

		cur_timer = SLIST_FIRST(&wfs_loop.timers);
		if (cur_timer) {
			if (os_get_time(&now)) {
				wfs_printf(LOG_ERR, "Failed to get "
					"current time.");
				goto out;
			}

			if (!os_is_time_before(&now, &cur_timer->tv)) {
				SLIST_REMOVE(&wfs_loop.timers, cur_timer,
					wfs_loop_timer, list_item);
				--wfs_loop.timer_num;
				cur_timer->handler(cur_timer->ctx);
				FREE(cur_timer);
			}
		}

		if (ret <= 0)
			continue;

		sock_list_dispatch(&wfs_loop.readers, &readfds);
	}

out:
	LEAVE();
	return;
}


void wfs_loop_deinit(void)
{
	ENTER();

	/** TODO: deinit wfs_loop */

	LEAVE();
}
